
// # JS 基本概念
// ## ES的几个重要版本
//     ES5 : 09年发布
//     ES6(ES2015) : 15年发布, 也称为ECMAScript2015
//     ES7(ES2016) : 16年发布, 也称为ECMAScript2016 (变化不大)

// 重要特性
//   JavaScript建立在一些非常优秀的想法和少数非常糟糕的想法之上。
//      优秀：函数、弱类型、动态对象和富有表现力的对象字面量
//      糟糕：基于全局变量的模型
//   JavaScript有非常强大的对象字面量表示法
//   JavaScript的函数是（主要）基于词法作用域的一级公民
//   原型继承是JavaScript中一个有争议的特性。这个系统中，对象直接从其他对象继承属性。


// ## 注释
// 单行注释
/*
  多行注释
  多行注释
  多行注释不能嵌套
*/
// ## js输出方式
console.log('这是 log');
console.info('这是 信息');
console.error('这是 错误');
console.warn('这是 警告');
// alert(1)
document.write(" Content from document.write");

// ## debugger
// debugger;

// ## js标志符 
// 作用： 用来给变量函数等命名的
// 1. 由字母，数字，下划线和$组成
// 2. 不能以数字开头
// 3. 不能是JS中的关键字和保留字
// 4. 区分大小写
// 5. 遵循驼峰原则（funName）第一个首字母小写，后面单词首字母大写
// 6. 见名知意

console.warn('JS数据类型');
// ## js数据类型
// 1. 数字（Number）
// 2. 字符串（String）
// 3. 布尔值（Boolean）
// 4. Undefined
// 5. Null
// 6. Symbol 它的功能类似于一种标识唯一性的ID 如 arr[Symbol.iterator]()

// ### Number
// 浮点数有精度问题
console.log('0.1 + 0.2 === 0.3', (0.1 + 0.2) === 0.3)
console.log('Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON', Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);

// Number - NaN 表示不是数字，但是其实它是一个特殊的数字（NaN：Not a Number）
console.log('NaN:', 1 * 'abc')
console.log('NaN === NaN', NaN === NaN)

// Number - Infinity Infinity：数据超过了JS可以表示的范围，是一个特殊的数字
console.log('Infinity', 1 / 0);

// Number - isNan(), isFinite()
console.log('isNaN', isNaN(NaN));
console.log('isFinite', isFinite(1 / 0));

// ### String
console.log('String')
console.log("String")
console.log("1 + 'abc'", 1 + 'abc')
console.log("1 / 'abc'", 1 / 'abc')

// ### Boolean
console.log('0', 0)
console.log('NaN', NaN)
console.log('""', "")
console.log('false', false)
console.log('undefined', undefined)
console.log('null', null)

// ### undefined  当声明的变量还未被初始化时，变量的默认值为undefined。
// 如果使用一个未定义的变量，会得到一个undefined值，当定义了一个变量未初始化，默认的也会给它初始化成undefined值
// null  null用来表示尚未存在的对象，常用来表示函数企图返回一个不存在的对象。
// 注意：null和undefined的最大区别是如果变量值为null，说明变量是存在的，只不过它的值是空值null

// ### typeof 
console.log('tyepof 1', typeof 1)
console.log('tyepof []', typeof [])
console.log('tyepof {}', typeof {})
console.log('tyepof " "', typeof " ")
console.log('tyepof function', typeof function () { })

// ### 通过 Object.prototype.toString 获取对象的类型
Object.prototype.toString.call('foo');     // "[object String]"
Object.prototype.toString.call([1, 2]);    // "[object Array]"
Object.prototype.toString.call(3);         // "[object Number]"
Object.prototype.toString.call(true);      // "[object Boolean]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null);      // "[object Null]"

console.warn('JS 操作符');
// # JS 操作符
// ## 其他基本类型转换为Number类型
console.log('Number(null)', Number(null)) // 0
console.log('Number(undefined)', Number(undefined)) // NaN
console.log('Number(true)', Number(true)) // 1
console.log('Number(false)', Number(false)) // 0
console.log('Number(123.4)', Number(123)) // 123.4

console.log('parseInt("12.3")', parseInt("12.3")) // 12
console.log('parseInt("123.4", 8)', parseInt("123.4", 8)) // 291
// console.log('Number.prototype', Number.prototype)
console.log('parseInt("123.4")', parseFloat("123.4")) //123.4

// ## 其他基本类型转为String类型
console.log('"hello"+null', "hello" + null)
console.log('"hello"+undefined', "hello" + undefined)
console.log('true.toString()', true.toString())
console.log('" " + 1:', " " + 1)
console.log('Number(10).toString()', Number(10).toString())

// var promptInt = prompt("请输入一个数字")
// console.log('prompt number is ', parseInt(promptInt))

// # 算数运算符和算数运算表达式
// 表达式（定义）: 由变量、常量和运算符组成的式子；一个表达式会产生一个值,它可以放在任何需要一个值的地方,比如,作为一个函数调用的参数
// 语句（定义）：
// 语句可以理解成一个行为.循环语句和if语句就是典型的语句.一个程序是由一系列语句组成的.
// JavaScript中某些需要语句的地方,你可以使用一个表达式来代替.这样的语句称之为表达式语句.但反过来不可以:你不能在一个需要表达式的地方放一个语句.比如,一个if语句不能作为一个函数的参数.

// 算数运算符 + - * / %（取余数）

// * 乘法
//   如果操作数都是数值，则执行常规的乘法运算，如果结果超过JS数值范围，则返回Infinity或者-Infinity

// / 除法
// 如果是非0的有限数被0除，则结果是Infinity或者-Infinity，取决于有符号操作数的符号
// 1 / 0， 结果为NaN

// % 取余

// - 减法
// 如果操作数都是数值，则执行常规的减法运算
// 如果有一个操作数是NaN，则返回NaN

// + 加法
// 如果操作数都是数值，则执行常规的加法运算
// 两个都是字符串，则拼接字符串
// 一个数值，一个字符串，则现将数值转为String类型，然后拼接字符串

// 自增自减运算符 ++ --
// 赋值运算符和赋值运算表达式 += -= *= /= %=

// 关系运算符和关系运算表达式
// > 大于 < 小于 >= 大于等于 <= 小于等于
// == 等于 ： 只比较数值，不比较类型。比较之前会进行类型统一
// === 绝对等于， 数值和类型都要相等才可

// 比较规则：
// 1、都是数值，正常比较
// 2、都是字符串，比较对应的字符编码值
// 3、一个是数值，则将另一个操作数转化为一个数值，然后进行数值比较
// 4、如果一个是对象，则调用这个对象的valueOf()方法，再按照上面规则比较；如果对象没有valueOf()方法，那么调用toString()方法，再按规则进行比较
// 5、如果一个是布尔值，那么先将其转化为数值，再进行比较

// 逻辑运算符
// 逻辑与（&&） 逻辑或（||） 逻辑非（!）

// ### 三目运算和三目运算符
//   表达式 ? 表达式1 : 表达式2
//   如果"表达式"的值为真，则取表达式1的计算值作为结果，否则取表达式2的计算值作为结果
var test3m = true
console.log(test3m ? 'return true' : 'return false')

console.warn('JS语句');
// # JS语句
// ## if  if-else  if-else-if
var ifCond = 8
if (ifCond === 1) {
  console.log('ifCond:', 1)
} else if (ifCond === 2) {
  console.log('ifCond:', 2)
} else {
  console.log('ifCond:', 3)
}

// ## switch break语句
var day = ''
switch (new Date().getDay()) {
  case 0:
    day = "星期天";
    break;
  case 1:
    day = "星期一";
    break;
  case 2:
    day = "星期二";
    break;
  case 3:
    day = "星期三";
    break;
  case 4:
    day = "星期四";
    break;
  case 5:
    day = "星期五";
    break;
  case 6:
    day = "星期六";
}


// ## while语句
var text = '', i=0
while (i < 10) {
  text += "数字" + i;
  i++;
}
console.log('测试 while 语句：', text)

// ## do while语句
var text = ""
var i = 0;
do {
    text += "数字为 " + i;
    i++;
}
while (i < 5) { 
  console.log('test do while:', text)
}
// ## for 语句
for (var i = 0; i < 9; i++) {

  if(i === 5) {
    continue;
  }
  console.log('for 语句', i);
  // more statements
}

// ## for in 语句
for (var i in [5, 4, 3, 2]) {
  // 拿到的i 是索引或对象的key
  console.log('for i in 5: ', i)
}
const obj = {
  e: 'ee',
  f: 'ff',
  g: 'gg'
}
for (var key in obj) {
  // 拿到的i 是索引或对象的key
  console.log('for key in obj: ', key, ' --- ', obj[key])
}

// ## 在ES6中,增加了一个for of循环,使用起来很简单
for(var v of ['h', 'i', 'j']) {
  console.log('for of ', v); // 拿到值本身
}
// var obj2 = {
//   data: [ 'hello', 'world' ],
//   [Symbol.iterator]() {
//     const self = this;
//     let index = 0;
//     return {
//       next() {
//         if (index < self.data.length) {
//           return {
//             value: self.data[index++],
//             done: false
//           };
//         } else {
//           return { value: undefined, done: true };
//         }
//       }
//     };
//   }
// };
// for(var i of obj2) {
//   console.log('for of self define iterator ', i)
// }
// for in总是得到对像的key或数组,字符串的下标,而for of和forEach一样,是直接得到值结果for of不能对象用


console.warn('字符串用法');
// 字符串用法
// js使用的是unicode字符集， 一般使用其中的utf-8，每个字符占用1-4个字节
// 有两种形式的字符串：基本类型，对象类型
//    对象类型的字符串封装了一些属性及方法，而基本类型则没有
//    可以直接用基本类型字符串调用字符串对象封装的属性及方法，无须手动转换

console.log( 'typeof "abc"', typeof "abc")
console.log( 'typeof new String("abc")', typeof new String("abc"))

var testStr = 'abcd𝌆中文字符'
console.log(testStr + '的长度', testStr.length)
console.log('使用for in 遍历')
for( var i in testStr) {
  console.log(testStr[i], 'testStr.charCodeAt ' + i + ' ', testStr.charCodeAt(i).toString(16))
}

console.log('使用for of 遍历')
for( var s of testStr) {
  console.log(s)
}
console.log('testStr.codePointAt ' + i + ' ', testStr.codePointAt(4), testStr.codePointAt(4).toString(16))


// # 字符串的常用方法
// ## charAt(index)
var testStr = "a中b文"
console.log('charAt', testStr.charAt(0), testStr.charAt(1))

// ## charCodeAt(index)
console.log('charCodeAt', testStr.charCodeAt(0), testStr.charCodeAt(1))

// ## fromCharCode
console.log('fromCharCode', String.fromCharCode(20013))

// ## toLowerCase(), toUpperCase()
var testStr = "aBcD"
console.log('toLowerCase toUpperCase:',testStr.toLowerCase(), testStr.toUpperCase())

// ## localeCompare()
console.log('localeCompare', '啊四'.localeCompare('张三'));
console.log('localeCompare', '2'.localeCompare('10',{ numeric: true}) )
console.log('localeCompare', '01啊四'.localeCompare('1张三',{ numeric: true}) )


// ## indexOf(searchValue, fromindex)
var testStr2 = "a中b文中"
console.log('indexOf', testStr2.indexOf('中'))

// ## lastIndexOf(searchValue)
console.log('lastIndexOf', testStr2.lastIndexOf('中'))

// ## replace(oldStr_or_regex, newStr)  默认只能替换第一次出现的oldStr
var testStr3 = "Tom is a good man ! Tom is a nice man"
console.log('replace common', testStr3.replace('Tom', 'Tony'))
console.log('replace regex', testStr3.replace(/Tom/g, 'Tony'))

// ## substring(start, stop)
var testStr4 = "a中b文中"
console.log('substring', testStr4.substring(0,2)) // 包含stop位所在的索引
console.log('substring', testStr4.substring(1,2)) 

// ## substr(start,length)
console.log('substr', testStr4.substr(1,2))

// ## stringObject.split(separator,howmany)
console.log('split', 'abaaaab aafefe'.split('b'))
console.log('split', 'abaaaab aafefe'.split())
console.log('split', 'abaaaab aafefe'.split(''))
console.log('split', 'abaaaab aafefe'.split('', 3))

// ## concat
console.log('concat', 'aa'.concat('bbb'))

// ## trim
console.log('trim', '|' +'  aa aa  '.trim() + "|")


// 内置对象是JS语言自带的对象，提供了一些基本的功能供开发者使用。常用的内置对象有Math，String，Array，Date等。

//  # Math对象
console.warn('Math对象')
console.log('四舍五入', Math.round(4.5))
console.log('向上取整', Math.ceil(3.1))
console.log('向下取整', Math.floor(3.9))
console.log('取最大值', Math.max(3, 4, 6, 76, 8, 89, 32))
console.log('取最小值', Math.min(3, 4, 6, 76, 8, 89, 32))
console.log('取绝对值', Math.abs(-10))
console.log('x的y次方', Math.pow(2, 4))
console.log('开平方', Math.sqrt(25))
console.log('生成一个0-1之间的随机数', Math.random())

// --
console.warn('JS数组用法')

var arr = new Array();
var array = [2, 3, 'Tom', true]

// ### length ,数组长度，自动变化
console.log('数组的长度', array.length)
array.length = 6; // 数组的长度增加，默认赋值为undefined
array.length = 3; // 数组的长度减少，多余删除
// delete
delete array[2]; // 通过delete操作符来删除指定的元素， 注：数组的长度不会变化，只是将对应位置的值删除为undefined

// ### push
array.push(4,5,6) // 在尾部添加一个或多个元素
// unshift
array.unshift(-3, -2,-1) // 在头部添加一个或多个元素
// ### pop
console.log('array pop', array.pop()) // 从尾部删除一个元素，并返回
// ### shift
console.log('array shift', array.shift()) // 从头部删除一个元素，并返回
// join
console.log('array join(str)', array.join('|')) // 字符串拼接，分割符为指定的str
// ### reverse
array.reverse()
console.log('array reverse', array) // 倒置数组元素，原数组顺序被倒置
// ### slice(startIndex, endIndex) ,在数组中，从startIndex下标开始一直截取到endIndex下标之前
console.log('array slice:', array.slice(1, 2), array) // 返回新数组，原数组不变
// ### splice(下标，个数，item1, item2, ...) , 在数组的中间删除并添加新元素
array.splice(2,2,0,0) // 从索引2开始，删除两个元素并添加 0 0两个元素，返回删除的元素
console.log('Array提供的方法', array)
// ### concat
console.log('array concat', array.concat([99, 99, 99])) // 返回新的拼接的数组， 原数组不变。
// ### indexOf lastIndexOf 从数组的左侧(右侧)开始查找对应第一次出现的参数元素的下标
console.log('indexOf 0', array.indexOf(0))
console.log('lastIndexOf 0', array.lastIndexOf(0))

// ## 数组排序 
// ### srot sort()调用每一项的toString()方法，然后比较得到的字符串，确定如何排序
array.sort(function(v1, v2) {
  if (v1 < v2) {
    return -1
  } else if (v1 === v2) {
    return 0
  } else {
    return 1
  }
})
console.log('Array提供的方法', array)

// ## 数组迭代
// ### every 对数组中的每一项运行给定函数，如果该数组中每一项都返回true，则返回true
var arr1 = [1, 2, 3 ,4 , 5]
console.log('array every', arr1.every(function(item, index, array) { return item > 2}) ) // 每一项都大于2 ，返回true，否则返回false
// ### filter
console.log('filter', arr1.filter( function (item, index, array){
  return item > 2
}), arr1)
// ### map
console.log('array map', arr1.map(function(item) { return item * 3 }))
// ### some
console.log('array some', arr1.some(function(item, index, array) { return item > 2}) ) // 只要有一向大于2 就返回true，否则返回false，跟every相反

// ## 数组 递归 reduce
console.log('array reduce', arr1.reduce(function(prev, cur, index, arr) {
  return prev + cur
}, 0))
console.log('array reduceRight', arr1.reduceRight(function(prev, cur, index, arr) {
  return prev + cur
}, 0))

var arr2 = [
  {
    key: 'key1',
    otherAttr: '1'
  },
  {
    key: 'key2',
    otherAttr: '2'
  },
  {
    key: 'key3',
    otherAttr: '3'
  },
  {
    key: 'key4',
    otherAttr: '4'
  },
  {
    key: 'key5',
    otherAttr: ''
  }
]
function getListByKey(list, key = 'key') {
  return list.reduce(
    (pre, curr) => (
      {
        ...pre,
        [curr[key]]: curr
      }
    ),
    {}
  )
}
console.log('reduct demo:', getListByKey(arr2))

// Ajax


function ajax(json){
  if(window.XMLHttpRequest){
    var ajax = new XMLHttpRequest();
  }
  else{
    var ajax = new ActiveXObject( "Microsoft.XMLHTTP" );
  }

  if(json.type=='get'){
    ajax.open('get',json.url+'?'+JsonToString(json.data),true);
    ajax.send();
  }
  else if(json.type=='post'){
    ajax.open('post',json.url,true);
    ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    ajax.send(JsonToString(json.data));
  }
  ajax.onreadystatechange = function(){
    if(ajax.readyState == 4){
        if(ajax.status>=200 && ajax.status<300 || ajax.status == 304){
            json.success(ajax.responseText);
        }
        else{
            json.error && json.error();
        }
    };
  };
  function JsonToString(json){
    var arr = [];
    for(var i in json){
        arr.push(i+'='+json[i]);
    };
    return arr.join('&');
  }
}
// 发送ajax请求
ajax({
  'url':'http://www.baotx2018.xyz:8083/v1/albums/all',
  'type':'get',
  'data':{
      user: 'zhangsan',
      passwd: '123123'
  },
  success:function(data){
      console.log('ajax return', data)
  }
})

// 原型 prototype
// 这个模型在ES的各个历史版本中并没有很大改变，但从 ES6 以来，JavaScript提供了一系列内置函数，以便更为直接地访问操纵原型。
// 三个方法分别为：
// Object.create 根据指定的原型创建新对象，原型可以是null；
// Object.getPrototypeOf 获得一个对象的原型；
// Object.setPrototypeOf 设置一个对象的原型。

// ES5定义对象属性的写法
// Object.defineProperty(person,'name',{
//   configurable:false,//能否使用delete、能否需改属性特性、或能否修改访问器属性、，false为不可重新定义，默认值为true
//   enumerable:false,//对象属性是否可通过for-in循环，flase为不可循环，默认值为true
//   writable:false,//对象属性是否可修改,flase为不可修改，默认值为true
//   value:'xiaoming' //对象属性的默认值，默认值为undefined
// })
console.warn('基于原型的基础：')
var catClass = {
  say(){
      console.log("meow~");
  },
  jump(){
      console.log("jump");
  }
}
var tigerClass = Object.create(catClass, {
  say: {
    writable:true, // 对象属性是否可修改,flase为不可修改，默认值为true
    configurable:true, // 能否使用delete、能否需改属性特性、或能否修改访问器属性、，false为不可重新定义，默认值为true
    enumerable:true, // 对象属性是否可通过for-in循环，flase为不可循环，默认值为true
    value: function() { // 对象属性的默认值，默认值为undefined
      console.log("roar!");
    } 
  }
})
var anotherCat = Object.create(catClass);
anotherCat.say();
var anotherTiger = Object.create(tigerClass);
anotherTiger.say();
console.log('原型 anotherTiger', anotherTiger, anotherTiger.__proto__ === tigerClass, anotherTiger.__proto__.__proto__ === catClass)


// DOM  document object model
window.onload = function() {
  var domChild = document.getElementById('child')
  console.log('domChild', domChild)
  // for(var attr in domChild) {
  //   console.log('attr:', attr)
  // }
}
// ES6 ES7


// https://www.cnblogs.com/wangfupeng1988/p/3977924.html
// JavaScript深入理解（1） --- 一切都是对象
// “一切都是对象”这句话的重点在于如何去理解“对象”这个概念。
// ——当然，也不是所有的都是对象，值类型就不是对象。
function show(x) {
  console.log("show typeof x", typeof x);    // undefined
  console.log("show typeof 10", typeof 10);   // number
  console.log("show typeof 'abc'", typeof 'abc'); // string
  console.log("show typeof true", typeof true);  // boolean
  console.log("show typeof function () {}", typeof function () {});  //function
  console.log("show typeof [1, 'a', true]", typeof [1, 'a', true]);  //object
  console.log("show typeof { a: 10, b: 20 }", typeof { a: 10, b: 20 });  //object
  console.log("show typeof null", typeof null);  //object
  console.log("show typeof new Number(10)", typeof new Number(10));  //object
}
show();

// 以上代码列出了typeof输出的集中类型标识，其中上面的四种（undefined, number, string, boolean）属于简单的值类型，不是对象。剩下的几种情况——函数、数组、对象、null、new Number(10)都是对象。他们都是引用类型。
// 判断一个变量是不是对象非常简单。值类型的类型判断用typeof，引用类型的类型判断用instanceof。

var fn = function () { };
console.log('fn instanceof Object', fn instanceof Object);  // true

// 那么在javascript中的对象，到底该如何定义呢
// 对象——若干属性的集合。
// java或者C#中的对象都是new一个class出来的，而且里面有字段、属性、方法，规定的非常严格。但是javascript就比较随意了——数组是对象，函数是对象，对象还是对象。
// 对象里面的一切都是属性，只有属性，没有方法。那么这样方法如何表示呢？——方法也是一种属性。因为它的属性表示为键值对的形式。
// 而且，更加好玩的事，javascript中的对象可以任意的扩展属性，没有class的约束。这个大家应该都知道，就不再强调了。

var objQ = {
  a: 10,
  b: function(x) {
    alert(this.a + x)
  },
  c: {
    name: '林二毛',
    gender: '男'
  }
}
// 这个可能比较好理解，那么函数和数组也可以这样定义属性吗？——当然不行，但是它可以用另一种形式，总之函数/数组之流，只要是对象，它就是属性的集合。
var fn = function () {
  alert(100);
};
fn.a = 10;
fn.b = function () {
  alert(123);
};
fn.c = {
  name: '林二毛',
  gender: '男'
};

// javascript与java/C#相比，首先最需要解释的就是弱类型。
// 其次要解释的就是本文的内容——一切（引用类型）都是对象，对象是属性的集合。


// JavaScript深入理解（2） --- 函数和对象的关系

// 对！函数是一种对象，但是函数却不像数组一样——你可以说数组是对象的一种，因为数组就像是对象的一个子集一样。
// 但是函数与对象之间，却不仅仅是一种包含和被包含的关系，函数和对象之间的关系比较复杂，函数可以攒创建对象，甚至有一点鸡生蛋蛋生鸡的逻辑

function Fn() {
  this.name = '林二毛';
  this.gender = '男';
}
var fn1 = new Fn();
// 对象可以通过函数来创建

//var obj = { a: 10, b: 20 };
//var arr = [5, 'x', true];
var objP = new Object();
objP.a = 10;
objP.b = 20;
var arrP = new Array();
arrP[0] = 5;
arrP[1] = 'x';
arrP[2] = true;

// 现在是不是糊涂了—— 对象是函数创建的，而函数却又是一种对象——天哪！函数和对象到底是什么关系啊？
// 别着急！揭开这个谜底，还得先去了解一下另一位老朋友——prototype原型。


// JavaScript深入理解（3） --- prototype原型
// 函数也是对象，所以他可以有自己的属性，函数默认有一个prototype属性
// 这个prototype的属性值是一个对象（属性的集合，再次强调！），默认的只有一个叫做constructor的属性，指向这个函数本身。

console.log('Object.prototype', Object.prototype)
// Object.prototype.toString
// Object.prototype.constructor
// Object.prototype.hasOwnProperty

function Fn() {}
Fn.prototype.name = '王福朋';
Fn.prototype.getYear = function () {
    return 2019;
};
var fnInstance = new Fn();
console.log(fnInstance.name);
console.log(fnInstance.getYear());
// 即，Fn是一个函数，fn对象是从Fn函数new出来的，这样fn对象就可以调用Fn.prototype中的属性。
// 因为每个对象都有一个隐藏的属性——“__proto__”，这个属性引用了创建这个对象的函数的prototype。即：fn.__proto__ === Fn.prototype
// 这里的"__proto__" 称为 “隐式原型”，

// JavaScript深入理解（4） --- __proto__隐式原型
// 每个函数function都有一个prototype，即原型。这里再加一句话——每个对象都有一个__proto__，可成为隐式原型。

var objR = new Object()
console.log('objR.__proto__ === Object.prototype:', objR.__proto__ === Object.prototype)

// 自定义函数的prototype。自定义函数的prototype本质上就是和 var obj = {} 是一样的，都是被Object创建，所以它的__proto__指向的就是Object.prototype
function selfDefineFun () {} 
// 自定义函数的原型 selfDefineFun.prototype本身是一个对象由Object()创建，所以他的__proto__ 指向 Object.prototype
// 那么 Object.prototyp.__proto__又指向谁呢， 指向了null

// 函数也不是从石头缝里蹦出来的，函数也是被创建出来的。谁创建了函数呢？——Function——注意这个大写的“F”。

// 1
function fn11(x, y) {
  return x + y
}
console.log(fn11(10, 20));
// 2
var fn12 = new Function("x", "y", "return x + y")
console.log(fn12(10, 20))
// 以上代码中，第一种方式是比较传统的函数创建方式，第二种是用new Functoin创建。不推荐第二种，
// 这里只是向大家演示，函数是被Function创建的。

// Function也是一个函数，函数是一种对象，也有__proto__属性。既然是函数，那么它一定是被Function创建。
      // 所以——Function是被自身创建的。所以它的__proto__指向了自身的Prototype


// JavaScript深入理解（5） --- instanceof
// 对于值类型，你可以通过typeof判断，string/number/boolean都很清楚，但是typeof在判断到引用类型的时候，返回值只有object/function，你不知道它到底是一个object对象，还是数组，还是new Number等等

// Instanceof运算符的第一个变量是一个对象，暂时称为A；第二个变量一般是一个函数，暂时称为B。
// Instanceof的判断队则是：沿着A的__proto__这条线来找，同时沿着B的prototype这条线来找，如果两条线能找到同一个引用，即同一个对象，那么就返回true。如果找到终点还未重合，则返回false。
console.log('Object instanceof Function', Object instanceof Function)
console.log('Function instanceof Object', Function instanceof Object)
console.log('Function instanceof Function', Function instanceof Function)
// instanceof表示的就是一种继承关系，或者原型链的结构

// JavaScript深入理解（6） --- 继承
// 原型链如果解释清楚了很容易理解，不会与常用的java/C#产生混淆。
// 而“继承”确实常用面向对象语言中最基本的概念，但是java中的继承与javascript中的继承又完全是两回事儿。
// javascript中的继承是通过原型链来体现的

function Foo() {}
var f1 = new Foo()
f1.a = 10
Foo.prototype.a = 100
Foo.prototype.b = 200
console.log(f1.a)
console.log(f1.b)

// 以上代码中，f1是Foo函数new出来的对象，f1.a是f1对象的基本属性;
// f1.b是怎么来的呢？——从Foo.prototype得来，因为f1.__proto__指向的是Foo.prototype
// 原型链: 访问一个对象的属性时，先在基本属性中查找，如果没有，再沿着__proto__这条链向上找，这就是原型链。

// 那么我们在实际应用中如何区分一个属性到底是基本的还是从原型中找到的呢？大家可能都知道答案了——hasOwnProperty，特别是在for…in…循环中，一定要注意。
// 对象的原型链是沿着__proto__这条线走的，因此在查找f1.hasOwnProperty属性时，就会顺着原型链一直查找到Object.prototype。
// 由于所有的对象的原型链都会找到Object.prototype，因此所有的对象都会有Object.prototype的方法。这就是所谓的“继承”
// 我们都知道每个函数都有call，apply方法，都有length，arguments，caller等属性。为什么每个函数都有？这肯定是“继承”的。函数由Function函数创建，因此继承的Function.prototype中的方法

// JavaScript深入理解（7） --- 原型的灵活性

var objS = { a: 10, b: 20}
var arrS = [1, 'f', true]
console.log(objS.toString()) // [object Object]
console.log(arrS.toString()) // 1, f , true

function Foo() {}
var f1 = new Foo()
Foo.prototype.toString = function() {
  return '返回固定值'
}
console.log(f1.toString())
// 一些框架为内置的构造函数Boolean，原型上添加犯方法
Boolean.prototype.toJSON = function() {
  return this.valueOf()
}

// JavaScript深入理解（8） --- 执行上下文（上）
// console.log('执行上下文1', a)
// console.log('执行上下文2',a)
// var a
console.log('执行上下文3',a)
var a = 10  // 变量提升

// console.log('执行上下文4',a)
// let a = 10

// 第一句报错，a未定义，很正常。第二句、第三句输出都是undefined，说明浏览器在执行console.log(a)时，已经知道了a是undefined，但却不知道a是10（第三句中）。
// 在一段js代码拿过来真正一句一句运行之前，浏览器已经做了一些“准备工作”，其中就包括对变量的声明，而不是赋值。变量赋值是在赋值语句执行的时候进行的。可用下图模拟
// let 是ES6中定义的，规定变量先定义后使用，a在定义前被使用，所以报错
console.log(this) // Window对象
// 第一种情况只是对变量进行声明（并没有赋值），而此种情况直接给this赋值。这也是“准备工作”情况要做的事情之一

console.log('f1', f1)
function f1() {}  // 函数声明

console.log('f2', f2) 
var f2 = function() {} // 函数表达式
// js在执行代码前会有个准备工作， 1 变量、函数表达式——变量声明，默认赋值为undefined， 2 this——赋值 3 函数声明——赋值
// 其实，javascript在执行一个代码段之前，都会进行这些“准备工作”来生成执行上下文。这个“代码段”其实分三种情况——全局代码，函数体，eval代码。


// JavaScript深入理解（9） --- 执行上下文（下）

function fnZ91(x) {
  console.log('fnZ91:')
  console.log(arguments)
  console.log(x)
}
fnZ91( 2, 3)

// 以上代码展示了在函数体的语句执行之前，arguments变量和函数的参数都已经被赋值。
// 从这里可以看出，函数每被调用一次，都会产生一个新的执行上下文环境。因为不同的调用可能就会有不同的参数

// 函数在定义的时候（不是调用的时候），就已经确定了函数体内部自由变量的作用域

var aZ91 = 10

function fnZ92() {
  console.log(aZ91) // aZ91是自由变量
                    // 函数创建时，就确定了a要取值的作用域
}

function barZ91(f) {
  var aZ91 = 20
  f()  // 打印 10， 而不是20
}
barZ91(fnZ92)

// 给执行上下文环境下一个通俗的定义——在执行代码之前，把将要用到的所有的变量都事先拿出来，有的直接赋值了，有的先用undefined占个空。
// 上下文环境的中的数据
//   1 全局上下文
//     1.1 普通变量的声明但不赋值， 函数表达式声明而不赋值
//     1.2 函数声明
//     1.3 this赋值
//   1 代码段是函数体
//     1.1 普通变量的声明但不赋值， 函数表达式声明而不赋值
//     1.2 函数声明
//     1.3 this赋值
//     1.4 参数赋值
//     1.5 arguments赋值
//     1.6 自由变量的取值作用域 赋值

// 讲完了上下文环境，又来了新的问题——在执行js代码时，会有数不清的函数调用次数，会产生许多个上下文环境。这么多上下文环境该如何管理，以及如何销毁而释放内存呢

// JavaScript深入理解（10） --- this
// 在函数中this到底取何值，是在函数真正被调用执行的时候确定的，函数定义的时候确定不了。
// 因为this的取值是执行上下文环境的一部分，每次调用函数，都会产生一个新的执行上下文环境。

// 情况1：构造函数
// 所谓构造函数就是用来new来创建对象的函数。其实严格来说，所有的函数都可以new一个对象，但是有些函数的定义是为了new一个对象，而有些函数则不是。另外注意，构造函数的函数名第一个字母大写（规则约定）。例如：Object、Array、Function等。

function FooZ101() {
  this.name = 'Jony'
  this.year = 2019
  console.log(this)
}
var f1 = new FooZ101()
console.log(f1.name)
console.log(f1.year)
// 以上代码中，如果函数作为构造函数用，那么其中的this就代表新new出来的对象
// 如果直接调用Foo函数，而不是new Foo()，情况就大不一样了，this指向了window，window会被新增name,year属性

// 情况2：函数作为对象的一个属性
// 如果函数作为对象的一个属性时，并且作为对象的一个属性被调用时，函数中的this指向该对象
var objZ102 = {
  x: 10,
  fn: function() {
    console.log(this)
    console.log(this.x)
  }
}
objZ102.fn()
// 以上代码中，fn不仅作为一个对象的一个属性，而且的确是作为对象的一个属性被调用。结果this就是obj对象。

var fnZ103Outer = objZ102.fn
fnZ103Outer()
// 如果fn函数被赋值到了另一个变量中，并没有作为obj的一个属性被调用，那么this的值就是window

// 情况3：函数用call或者apply调用
var objZ104 = {
  x: 'Z104'
}
var fnZ105 = function() {
  console.log(this)
  console.log(this.x)
}
fnZ105.call(objZ104)

// 情况4：全局 & 调用普通函数
// 在全局环境下，this永远是window，这个应该没有非议。
// 普通函数在调用时，其中的this也都是window。
console.log('全局环境', this)
var fnZ106 = function() {
  console.log(this)  // window
  console.log(this.x)
}

var obj107 = {
  x: '107',
  fn: function() {
    function f() { 
      console.log('107', this)  // window
      console.log('107', this.x)
    }
    f()
  }
}
obj107.fn()
// 函数f虽然是在obj.fn内部定义的，但是它仍然是一个普通的函数，this仍然指向window

// 情况5 整个原型链中，this代表的也都是当前对象的值。
function FooZ108() {
  this.name = '李雷'
  console.log(this)
}
FooZ108.prototype.getName = function() {
  console.log(this.name)
}
var obj109 = new FooZ108()
obj109.getName()

// JavaScript深入理解（11） --- 执行上下文栈
// 执行全局代码时，会产生一个执行上下文环境，每次调用函数都又会产生执行上下文环境。当函数调用完成时，这个上下文环境以及其中的数据都会被消除，再重新回到全局上下文环境。处于活动状态的执行上下文环境只有一个

// 全局环境1 a fn bar this
var a = 10, 
  fn, 
  bar = function(x) {
    // 环境2 this arguments x, b, fn
    var b = 5 
    fn(x + b)
  }
fn = function(y) {
  // 环境3 this arguments y c
  var c = 5;
  console.log(y + c)
}
bar(10);
// 上面的代码演示的比较理想的情况

// JavaScript深入理解（12） ---  简介 作用域
// javascript没有块级作用域”。所谓“块”，就是大括号“｛｝”中间的语句。
// var i = 10
// if (i > 1) {
//   var whatName = "林俊杰"
// }
// console.log('whatName:', whatName) // 林俊杰" undefined

// ES5 中 javascript除了全局作用域之外，只有函数可以创建的作用域
// 所以，我们在声明变量时，全局代码要在代码前端声明，函数中要在函数体一开始就声明好。除了这两个地方，其他地方都不要出现变量声明。而且建议用“单var”形式。

// 作用域定义
// 作用域是一个很抽象的概念，类似于一个“地盘”
// 图 js-作用域.png
// 如上图，全局代码和fn、bar两个函数都会形成一个作用域。而且，作用域有上下级的关系，上下级关系的确定就看函数是在哪个作用域下创建的。例如，fn作用域下创建了bar函数，那么“fn作用域”就是“bar作用域”的上级
// 作用域最大的用处就是隔离变量，不同作用域下同名变量不会有冲突。例如以上代码中，三个作用域下都声明了“a”这个变量，但是他们不会有冲突。各自的作用域下，用各自的“a”。
// (function(window, undefined) {
//   var a, b, comments
//   // ......
// })(window)

// JavaScript深入理解（13） ---  作用域 和 上下文环境
// 如上图，我们在上文中已经介绍了，除了全局作用域之外，每个函数都会创建自己的作用域，作用域在函数定义时就已经确定了。而不是在函数调用时确定。
// 输入 JavaScript深入理解（13） 的代码进行解释
// 连接起来看，还是挺有意思的。作用域只是一个“地盘”，一个抽象的概念，其中没有变量。要通过作用域对应的执行上下文环境来获取变量的值。同一个作用域下，不同的调用会产生不同的执行上下文环境，继而产生不同的变量的值。所以，作用域中变量的值是在执行过程中产生的确定的，而作用域却是在函数创建时就确定了。
// 所以，如果要查找一个作用域下某个变量的值，就需要找到这个作用域对应的执行上下文环境，再在其中寻找变量的值。

// JavaScript深入理解（14） ---  从 自由变量 到 作用域链
// 在A作用域中使用的变量x，却没有在A作用域中声明（即在其他作用域中声明的），对于A作用域来说，x就是一个自由变量。如下图

// var x = 10
// function fn() {
//   var b = 20
//   console.log( x +  b) // 这里x就是一个自由变量
// }
// 要到创建这个函数的那个作用域中取值——是“创建”，而不是“调用”，切记切记 ——其实这就是所谓的“静态作用域”。 
// 上面描述的只是跨一步作用域去寻找。如果跨了一步，还没找到呢？——接着跨！——一直跨到全局作用域为止。要是在全局作用域中都没有找到，那就是真的没有了。
// 这个一步一步“跨”的路线，我们称之为——作用域链。
// 我们拿文字总结一下取自由变量时的这个“作用域链”过程：（假设a是自由量）
// 第一步，现在当前作用域查找a，如果有则获取并结束。如果没有则继续；
// 第二步，如果当前作用域是全局作用域，则证明a未定义，结束；否则继续；
// 第三步，（不是全局作用域，那就是函数作用域）将创建该函数的作用域作为当前作用域；
// 第四步，跳转到第一步。


// JavaScript深入理解（15） --- 闭包
// 闭包的定义：闭包就是能够读取其他函数内部变量的函数。
// 闭包的作用：
//   A、可以读取函数内部的变量
//   B、让这些变量的值始终保存在内存中。这是因为闭包的执行依赖外部函数中的比那辆，只有闭包执行完，才会释放变量所占的内存
// 定义很抽象，但是你只需要知道应用的两种情况即可——函数作为返回值，函数作为参数传递。

// 我们提到当一个函数被调用完成之后，其执行上下文环境将被销毁，其中的变量也会被同时销毁。
// 但是在当时那篇文章中留了一个问号——有些情况下，函数调用完成之后，其执行上下文环境不会接着被销毁。这就是需要理解闭包的核心内容
// 第三步，执行完第17行，fn()调用完成。按理说应该销毁掉fn()的执行上下文环境，但是这里不能这么做。注意，重点来了：因为执行fn()时，返回的是一个函数。函数的特别之处在于可以创建一个独立的作用域。而正巧合的是，返回的这个函数体中，还有一个自由变量max要引用fn作用域下的fn()上下文环境中的max。因此，这个max不能被销毁，销毁了之后bar函数中的max就找不到值了。
// 因此，这里的fn()上下文环境不能被销毁，还依然存在与执行上下文栈中。
// 使用闭包会增加内容开销


// JavaScript深入理解（18） --- 上下文环境 和 作用域的关系

// 00 上下文环境：
// 可以理解为一个看不见摸不着的对象（有若干个属性），虽然看不见摸不着，但确实实实在在存在的，因为所有的变量都在里面存储着，要不然咱们定义的变量在哪里存？
// 另外，对于函数来说，上下文环境是在调用时创建的，这个很好理解。拿参数做例子，你不调用函数，我哪儿知道你要给我传什么参数？

// 01 作用域：
// 首先，它很抽象。第二，记住一句话：除了全局作用域，只有函数才能创建作用域。创建一个函数就创建了一个作用域，无论你调用不调用，函数只要创建了，它就有独立的作用域，就有自己的一个“地盘”。
// 02 两者：
// 一个作用域下可能包含若干个上下文环境。有可能从来没有过上下文环境（函数从来就没有被调用过）；有可能有过，现在函数被调用完毕后，上下文环境被销毁了；有可能同时存在一个或多个（闭包）。

(function() {
  var x = 100
  function fn(x) {
    var xx = 999
    var reFun = function() {
      console.log(x, xx)
    }
    reFun.changeXX = function(p) {
      xx = p
    }
    return reFun
  }
  var f1 = fn(5)
  f1.changeXX(88)
  var f2 = fn(10)
  f2.changeXX(77)
  f1()
  f2()
})()


// 浏览器工作原理

